/*
    libhdvbd - Library for building sophisticated DVB applications
    Copyright (C) 2012 Tony Houghton <h@realh.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Connects to an HdvbChannelScan's signals to add data to a database.
 * Automatically frees itself on cancelled or completed signals.
 */

#include <glib.h>

#include <string.h>

#include "chanscan-factory.h"
#include "chanscantodb.h"
#include "chansdb.h"
#include "dvbenc.h"

#define HDVB_T2_PROCESSED_FLAG ((guint64) 1 << 48)

void
hdvb_chan_scan_to_db_delete(HdvbChanScanToDb *cs2db)
{
    if (cs2db->del)
        cs2db->del(cs2db);
    else
        hdvb_chan_scan_to_db_standard_delete(cs2db);
}

void
hdvb_chan_scan_to_db_standard_delete(HdvbChanScanToDb *cs2db)
{
    if (cs2db->cancelled_tag)
    {
        g_signal_handler_disconnect(cs2db->scanner, cs2db->cancelled_tag);
        cs2db->cancelled_tag = 0;
    }
    if (cs2db->completed_tag)
    {
        g_signal_handler_disconnect(cs2db->scanner, cs2db->completed_tag);
        cs2db->completed_tag = 0;
    }
    if (cs2db->service_tag)
    {
        g_signal_handler_disconnect(cs2db->scanner, cs2db->service_tag);
        cs2db->service_tag = 0;
    }
    if (cs2db->transport_tag)
    {
        g_signal_handler_disconnect(cs2db->scanner, cs2db->transport_tag);
        cs2db->transport_tag = 0;
    }
    if (cs2db->lcn_tag)
    {
        g_signal_handler_disconnect(cs2db->scanner, cs2db->lcn_tag);
        cs2db->lcn_tag = 0;
    }
    if (cs2db->dvbt2_tag)
    {
        g_signal_handler_disconnect(cs2db->scanner, cs2db->dvbt2_tag);
        cs2db->dvbt2_tag = 0;
    }
    if (cs2db->service_stmt)
    {
        sqlite3_finalize(cs2db->service_stmt);
        cs2db->service_stmt = NULL;
    }
    if (cs2db->transport_stmt)
    {
        sqlite3_finalize(cs2db->transport_stmt);
        cs2db->transport_stmt = NULL;
    }
    if (cs2db->lcn_stmt)
    {
        sqlite3_finalize(cs2db->lcn_stmt);
        cs2db->lcn_stmt = NULL;
    }
    if (cs2db->dvbt2_stmt)
    {
        sqlite3_finalize(cs2db->dvbt2_stmt);
        cs2db->dvbt2_stmt = NULL;
    }
    if (cs2db->lookup_dvbt2_stmt)
    {
        sqlite3_finalize(cs2db->lookup_dvbt2_stmt);
        cs2db->lookup_dvbt2_stmt = NULL;
    }
    if (cs2db->update_dvbt2_stmt)
    {
        sqlite3_finalize(cs2db->update_dvbt2_stmt);
        cs2db->update_dvbt2_stmt = NULL;
    }
    HLOG(g_object_unref(cs2db->scanner));
    cs2db->scanner = NULL;
    
    g_free(cs2db);
}

static void
hdvb_chan_scan_to_db_cancelled_cb(HdvbChannelScan *scanner, gpointer handle)
{
    hdvb_chan_scan_to_db_delete(handle);
}

static void
hdvb_chan_scan_to_db_completed_cb(HdvbChannelScan *scanner, gpointer handle)
{
    HdvbChanScanToDb *cs2db = handle;
    
    hdvb_chan_scan_to_db_delete(cs2db);
}

static void
hdvb_chan_scan_to_db_service_cb(HdvbChannelScan *scanner,
        const HdvbServiceDetails *details, gpointer handle)
{
    HdvbChanScanToDb *cs2db = handle;
    sqlite3_stmt *stmt = cs2db->service_stmt;
                    
    sqlite3_bind_int(stmt, 1, details->service_id);
    sqlite3_bind_int(stmt, 2, details->ts_id);
    sqlite3_bind_int(stmt, 3, details->free_ca_mode);
    sqlite3_bind_int(stmt, 4, details->service_type);
    sqlite3_bind_int(stmt, 5, 0);
    sqlite3_bind_text(stmt, 6, details->service_name, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 7, details->provider_name, -1, SQLITE_TRANSIENT);
    hdvb_db_step(stmt);
    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);
}

static void
hdvb_chan_scan_to_db_transport_cb(HdvbChannelScan *scanner,
        int ts_id, int network_id, const char *tuning_str, gpointer handle)
{
    HdvbChanScanToDb *cs2db = handle;
    sqlite3_stmt *stmt = cs2db->transport_stmt;
                    
    sqlite3_bind_int(stmt, 1, ts_id);
    sqlite3_bind_int(stmt, 2, network_id);
    sqlite3_bind_text(stmt, 3, tuning_str, -1, SQLITE_TRANSIENT);
    hdvb_db_step(stmt);
    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);
}

static void
hdvb_chan_scan_to_db_lcn_cb(HdvbChannelScan *scanner,
        int sec_id, int service_id, int region, int lcn, gpointer handle)
{
    HdvbChanScanToDb *cs2db = handle;
    sqlite3_stmt *stmt = cs2db->lcn_stmt;
                    
    sqlite3_bind_int(stmt, 1, service_id);
    sqlite3_bind_int(stmt, 2, sec_id);
    sqlite3_bind_int(stmt, 3, region);
    sqlite3_bind_int(stmt, 4, lcn);
    hdvb_db_step(stmt);
    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);
}

static void
hdvb_chan_scan_to_db_dvbt2_cb(HdvbChannelScan *scanner,
        int ts_id, int plp_id, int system_id, gpointer handle)
{
    HdvbChanScanToDb *cs2db = handle;
    sqlite3_stmt *stmt = cs2db->dvbt2_stmt;
    int n;
    gboolean new_t2 = TRUE;
                    
    if (!cs2db->t2_ids)
    {
        cs2db->t2_ids = g_array_new(FALSE, FALSE, 8);
    }
    else
    {
        for (n = 0; n < cs2db->t2_ids->len; ++n)
        {
            if ((g_array_index(cs2db->t2_ids, guint64, n) & 0xffff) == ts_id)
            {
                new_t2 = FALSE;
                break;
            }
        }
    }
    if (new_t2)
    {
        guint64 val = ts_id | (plp_id << 16) | ((guint64) system_id << 32);
        g_array_append_val(cs2db->t2_ids, val);
        sqlite3_bind_int(stmt, 1, ts_id);
        sqlite3_bind_int(stmt, 2, plp_id);
        sqlite3_bind_int(stmt, 3, system_id);
        hdvb_db_step(stmt);
        sqlite3_clear_bindings(stmt);
        sqlite3_reset(stmt);
    }
}

/* Returns TRUE iff a matching ts_id is found in the transports table */
static gboolean
process_new_t2_ids(HdvbChanScanToDb *cs2db,
        int ts_id, int plp_id, int system_id)
{
    gboolean result = FALSE;
    sqlite3_stmt *stmt = cs2db->lookup_dvbt2_stmt;
    
    sqlite3_bind_int(stmt, 1, ts_id);
    if (hdvb_db_step(stmt) == SQLITE_ROW)
    {
        char **tuning_split = g_strsplit(
                (const char *) sqlite3_column_text(stmt, 0), " ", 0);
        char *tuning_str;
        sqlite3_stmt *stmt2 = cs2db->update_dvbt2_stmt;
        
        if (!tuning_split[0][1])
        {
            char **tmp = tuning_split;
            
            tuning_split = g_new0(char *, 12);
            memcpy(tuning_split + 3, tmp + 1, 8 * sizeof(char *));
            g_free(tmp);
        }
        else
        {
            g_free(tuning_split[1]);
            g_free(tuning_split[2]);
        }
        tuning_split[1] = g_strdup_printf("%d", plp_id);
        tuning_split[2] = g_strdup_printf("%d", system_id);
        tuning_str = g_strjoinv(" ", tuning_split);
        g_strfreev(tuning_split);
        sqlite3_bind_text(stmt2, 1, tuning_str, -1, SQLITE_TRANSIENT);
        sqlite3_bind_int(stmt2, 2, ts_id);
        hdvb_db_step(stmt2);
        sqlite3_clear_bindings(stmt2);
        sqlite3_reset(stmt2);
        g_free(tuning_str);
        result = TRUE;
    }
    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);
    return result;
}

static void
hdvb_chan_scan_to_db_new_ts_cb(HdvbChannelScan *scanner,
        const char *tuning_str, int count, int total, gpointer handle)
{
    HdvbChanScanToDb *cs2db = handle;
    
    g_debug("New transport %02d/%02d '%s'", count, total, tuning_str);
    if (cs2db->t2_ids)
    {
        int n;
        
        for (n = 0; n < cs2db->t2_ids->len; ++n)
        {
            guint64 val = g_array_index(cs2db->t2_ids, guint64, n);
            
            if (!(val & HDVB_T2_PROCESSED_FLAG))
            {
                if (process_new_t2_ids(cs2db, val & 0xffff,
                        (val >> 16) & 0xffff, (val >> 32) & 0xffff))
                {
                    g_array_index(cs2db->t2_ids, guint64, n) =
                            val & ~HDVB_T2_PROCESSED_FLAG;
                }
            }
        }
    }
}

gboolean
hdvb_chan_scan_to_db_init(HdvbChanScanToDb *cs2db,
        sqlite3 *db, const char *factory)
{
    HdvbChannelScanFactory scan_creator =
            hdvb_chanscan_factory_lookup_scanner(factory);
    
    if (!scan_creator)
        return FALSE;
    cs2db->scanner = scan_creator();
    cs2db->del = hdvb_chan_scan_to_db_standard_delete;
    cs2db->db = db;
    cs2db->service_stmt = hdvb_chans_db_statement_insert_service(db);
    cs2db->transport_stmt = hdvb_chans_db_statement_insert_transport(db);
    cs2db->lcn_stmt = hdvb_chans_db_statement_insert_lcn(db);
    cs2db->dvbt2_stmt = hdvb_chans_db_statement_insert_dvbt2_ids(db);
    cs2db->lookup_dvbt2_stmt =
            hdvb_chans_db_statement_lookup_tuning_for_ts_id(db);
    cs2db->update_dvbt2_stmt =
            hdvb_chans_db_statement_update_tuning_for_ts_id(db);
    cs2db->cancelled_tag = hdvb_channel_scan_connect__cancelled(cs2db->scanner,
            hdvb_chan_scan_to_db_cancelled_cb, cs2db);
    cs2db->completed_tag = hdvb_channel_scan_connect__completed(cs2db->scanner,
            hdvb_chan_scan_to_db_completed_cb, cs2db);
    if (cs2db->service_stmt)
    {
        cs2db->service_tag = hdvb_channel_scan_connect__service_discovered(
                cs2db->scanner, hdvb_chan_scan_to_db_service_cb, cs2db);
    }
    if (cs2db->transport_stmt)
    {
        cs2db->transport_tag = hdvb_channel_scan_connect__transport_discovered(
                cs2db->scanner, hdvb_chan_scan_to_db_transport_cb, cs2db);
    }
    if (cs2db->lcn_stmt)
    {
        cs2db->lcn_tag = hdvb_channel_scan_connect__lcn_discovered(
                cs2db->scanner, hdvb_chan_scan_to_db_lcn_cb, cs2db);
    }
    if (cs2db->dvbt2_stmt)
    {
        cs2db->dvbt2_tag = hdvb_channel_scan_connect__dvb_t2_ids_discovered(
                cs2db->scanner, hdvb_chan_scan_to_db_dvbt2_cb, cs2db);
        if (cs2db->lookup_dvbt2_stmt && cs2db->update_dvbt2_stmt)
        {
            cs2db->new_ts_tag = hdvb_channel_scan_connect__new_transponder(
                    cs2db->scanner, hdvb_chan_scan_to_db_new_ts_cb, cs2db);
        }
    }
    return cs2db->service_stmt && cs2db->transport_stmt && cs2db->lcn_stmt &&
            cs2db->dvbt2_stmt && cs2db->lookup_dvbt2_stmt &&
            cs2db->update_dvbt2_stmt;
}

HdvbChanScanToDb *
hdvb_chan_scan_to_db_new(sqlite3 *db, const char *factory)
{
    HdvbChanScanToDb *cs2db = g_new0(HdvbChanScanToDb, 1);
    
    if (!hdvb_chan_scan_to_db_init(cs2db, db, factory))
    {
        hdvb_chan_scan_to_db_standard_delete(cs2db);
        return NULL;
    }
    return cs2db;
}
